/*
 * Copyright 2008 Neil Bacon
 * Distributed under the terms of the Mozilla Public License version 1.1
 * or the GNU General Public License version 3.
 */

package nb.xadd.node.impl;


import nb.cache.CacheSet;
import nb.util.Utils;
import nb.xadd.node.Node;
import nb.xadd.node.NodeFactory;
import nb.xadd.node.impl.PolynomialValueNode.PolynomialCoefficients;

/**
 * NodeFactory that rounds real values in terminal nodes and tests to a multiple of the resolution
 * (to increase Node caching) and reuses Nodes from the nodeCache (to save memory and allow
 * tests of object identity to determine equivalence).
 */
public class NodeFactoryImpl implements NodeFactory {

    private final double resolution;
    private final CacheSet<Node> nodeCache;

    /**
     * @param resolution for real values in terminal nodes and tests
     * @param nodeCache reuse Nodes from cache - never produce a duplicate object
     */
    public NodeFactoryImpl(double resolution, CacheSet<Node> nodeCache) {
        super();
        this.resolution = resolution;
        this.nodeCache = nodeCache;
    }
    
    private double round(double value) {
        return Utils.round(value, resolution);
    }

    private double[] round(double[] values) {
        return Utils.round(values, resolution);
    }

    @Override
    public Node getConstantValueNode(double constValue) {
        Node node = new ConstantValueNode(round(constValue));
        return nodeCache.get(node);
    }

    @Override
    public Node getLinearValueNode(double constValue, double[] coeff) {
        Node node = new LinearValueNode(round(constValue), round(coeff));
        return nodeCache.get(node);
    }

    @Override
    public Node getPolynomialValueNode(int numberOfVariables, double constValue, PolynomialCoefficients[] coeff) {
        Node node = new PolynomialValueNode(numberOfVariables, round(constValue), coeff);
        return nodeCache.get(node);
    }

    @Override
    public Node getBooleanDecisionNode(int booleanVarIndex, Node trueChild, Node falseChild) {
        // If trueChild redundant replace with its trueChild
        if (trueChild instanceof BooleanDecisionNode) {
            BooleanDecisionNode b = (BooleanDecisionNode) trueChild;
            if (b.getBooleanVarIndex() == booleanVarIndex) {
                trueChild = b.getTrueChild();
            }
        }
        // If falseChild redundant replace with its falseChild
        if (falseChild instanceof BooleanDecisionNode) {
            BooleanDecisionNode b = (BooleanDecisionNode) falseChild;
            if (b.getBooleanVarIndex() == booleanVarIndex) {
                falseChild = b.getFalseChild();
            }
        }
        // If branches redundant, return child
        if (trueChild == falseChild) {
            return trueChild;
        }
        Node node = new BooleanDecisionNode(booleanVarIndex, trueChild, falseChild);
        return nodeCache.get(node);
    }

    @Override
    public Node getRealEqualDecisionNode(int realVarIndex, double testValue, Node trueChild, Node falseChild) {
        testValue = round(testValue);
        if (trueChild instanceof AbstractRealDecisionNode) {
            AbstractRealDecisionNode r = (AbstractRealDecisionNode) trueChild;
            if (r.getRealVarIndex() == realVarIndex) {
                // trueChild test redundant, replace with its true/falseChild
                trueChild = r.getTestValue() == testValue ? r.getTrueChild() : r.getFalseChild();
            }
        }
        if (falseChild instanceof RealEqualDecisionNode) {
            RealEqualDecisionNode r = (RealEqualDecisionNode) falseChild;
            if (r.getRealVarIndex() == realVarIndex && r.getTestValue() == testValue) {
                // falseChild test redundant, replace with its falseChild
                falseChild = r.getFalseChild();
            }
        }
        // If branches redundant, return child
        if (trueChild == falseChild) {
            return trueChild;
        }
        Node node = new RealEqualDecisionNode(realVarIndex, testValue, trueChild, falseChild);
        return nodeCache.get(node);
    }

    @Override
    public Node getRealGreaterOrEqualDecisionNode(int realVarIndex, double testValue, Node trueChild, Node falseChild) {
        testValue = round(testValue);
        if (trueChild instanceof RealGreaterOrEqualDecisionNode) {
            RealGreaterOrEqualDecisionNode r = (RealGreaterOrEqualDecisionNode) trueChild;
            if (r.getRealVarIndex() == realVarIndex) {
                if (r.getTestValue() <= testValue) {
                    trueChild = r.getTrueChild(); // trueChild test redundant, replace with its trueChild
                } else if (r.getFalseChild() == falseChild) {
                    return trueChild;             // new test is redundant
                }
            }
        }
        if (falseChild instanceof RealGreaterOrEqualDecisionNode) {
            RealGreaterOrEqualDecisionNode r = (RealGreaterOrEqualDecisionNode) falseChild;
            if (r.getRealVarIndex() == realVarIndex) {
                if (r.getTestValue() >= testValue) {
                    falseChild = r.getFalseChild(); // falseChild test redundant, replace with its falseChild
                } else if (r.getTrueChild() == trueChild) {
                    return falseChild;              // new test is redundant
                }
            }
        }
        // If branches redundant, return child
        if (trueChild == falseChild) {
            return trueChild;
        }
        Node node = new RealGreaterOrEqualDecisionNode(realVarIndex, testValue, trueChild, falseChild);
        return nodeCache.get(node);
    }

}
